package src.ee.ioc.smaug.world.evolution;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.io.File;

import src.ee.ioc.smaug.parser.FSMXMLWriter;
import src.ee.ioc.smaug.parser.ParameterXMLWriter;
import src.ee.ioc.smaug.world.beings.*;
import src.ee.ioc.smaug.world.*;

/**
 * The whole evolution process
 * 
 * @author Margarita Spichakova
 * 
 */
public class Evolution {
    /**
     * Population
     */
    public static ArrayList<Individual> population;

    /**
     * Population for selection
     */
    public static ArrayList<Individual> newPopulation;

    /**
     * Number of generations which was required to solve task
     */
    public static int solvedIn = 0;

    /**
     * Fitness of best individual
     */
    static float solvedFitness = 0;

    /**
     * Number of individual with maximal fitness
     */
    private static int max = 0;

    /**
     * Individual for stroing the best solution
     */
    public static Individual solution = null;

    private static double[][] evolutionTrace;

    private static int step = 1;

    private static int position = 0;

    private static int iteratorTrace = 0;

    private static void fillEvolutionTrace() {
        evolutionTrace[iteratorTrace][0] = population.get(max).getFitness();
        for (int i = 0; i < Enviroment.inputData.size(); i++) {
            evolutionTrace[iteratorTrace][i + 1] = Enviroment.outputData.get(i)
                    .length()
                    - population.get(max).stringEvaluation[i];
        }
    }

    public static void printEvolutionTrace() {
        try {
            Enviroment.trace = new PrintWriter(new BufferedWriter(
                    new FileWriter(EnvDescription.traceFile)));

            StringBuffer s = new StringBuffer();
            s.append("<html><body>");
            s
                    .append("<table  border=\"1\" cellspacing=\"0\" cellpadding=\"0\">");
            s.append("<tr><th>Gen</th><th>Fitness</th>");
            s.append("<th colspan=");
            s.append(Enviroment.inputData.size());
            s.append(">String Errors</th>");

            s.append("</tr>");
            for (int i = 0; i < iteratorTrace; i++) {
                double value = 0;

                s.append("<tr>");
                s.append("<td>");
                s.append(i * step);
                s.append("</td>");
                s.append("<td>");
                s.append(Math.round(evolutionTrace[i][0]
                        / Enviroment.bestFitness * 100));
                s.append("%");
                s.append("</td>");
                for (int j = 0; j < Enviroment.inputData.size(); j++) {
                    value = evolutionTrace[i][j + 1]
                            / Enviroment.outputData.get(0).length() * 100;
                    if ((value > 0) && (value <= 10)) {
                        s.append("<td bgcolor=\"#CCCCCC\">");
                    } else if ((value > 10) && (value <= 20)) {
                        s.append("<td bgcolor=\"#999999\">");
                    } else if ((value > 20) && (value <= 30)) {
                        s.append("<td bgcolor=\"#666666\">");
                    } else if ((value > 30) && (value <= 50)) {
                        s.append("<td bgcolor=\"#333333\">");
                    } else if ((value > 50) && (value <= 100)) {
                        s.append("<td bgcolor=\"#000000\">");
                    } else if (value == 0) {
                        s.append("<td bgcolor=\"#FFFFFF\">");
                    }
                    s.append(evolutionTrace[i][j + 1]);
                    s.append("&nbsp;");
                    s.append("</td>");
                }

                s.append("</tr>");
            }
            s.append("<tr>");
            s.append("<td>");
            s.append("Res");
            s.append("</td>");
            s.append("<td>");
            s.append(Math.round(solution.getFitness() / Enviroment.bestFitness
                    * 100));
            s.append("%</td>");
            for (int j = 0; j < Enviroment.inputData.size(); j++) {
                double v1 = Enviroment.outputData.get(j).length()
                        - solution.stringEvaluation[j];
                double value = 0;
                value = (v1 / Enviroment.outputData.get(j).length() * 100);
                if ((value > 0) && (value <= 10)) {
                    s.append("<td bgcolor=\"#CCCCCC\">");
                } else if ((value > 10) && (value <= 20)) {
                    s.append("<td bgcolor=\"#999999\">");
                } else if ((value > 20) && (value <= 30)) {
                    s.append("<td bgcolor=\"#666666\">");
                } else if ((value > 30) && (value <= 50)) {
                    s.append("<td bgcolor=\"#333333\">");
                } else if ((value > 50) && (value <= 100)) {
                    s.append("<td bgcolor=\"#000000\">");
                } else if (value == 0) {
                    s.append("<td bgcolor=\"#FFFFFF\">");
                }

                s.append((double) Enviroment.outputData.get(j).length()
                        - solution.stringEvaluation[j]);
                s.append("</td>");
                s.append("</td>");

            }

            s.append("</tr>");
            s.append("</table>");
            s.append("</body></html>");
            Enviroment.trace.println(s.toString());

            iteratorTrace = 0;
        } catch (IOException e) {
            Enviroment.logger.severe("Error in input/output process...");
        } finally {
            Enviroment.trace.close();
        }
    }

    /**
     * The process of evolution
     */
    public static void evolute() {
        int gen = 0;
        if (EnvDescription.numberOfGenerations > 50) {
            step = (Integer) EnvDescription.numberOfGenerations / 50;
        }
        evolutionTrace = new double[51][1 + Enviroment.inputData.size()];

        Enviroment.logger.fine("Evolution process started");

        generateInitialPopulation();

        Enviroment.logger.fine("Initial population was generated");

        evaluatePop();

        Enviroment.logger.fine("Initial population was evaluated");

        if (EnvDescription.action == EnvDescription.RUN_ONCE) {
            fillEvolutionTrace();
            iteratorTrace++;
            position += step;
        }

        solution = population.get(max);

        max = 0;
        while ((gen < EnvDescription.numberOfGenerations)) {
            if (foundTheBest()) {
                break;
            }
            selection();

            Enviroment.logger.fine("Selection process is over");

            breeding();

            Enviroment.logger.fine("New population " + gen + " was created");

            evaluatePop();
            if (gen == position) {
                fillEvolutionTrace();
                iteratorTrace++;
                position += step;

            }
            Enviroment.logger.fine("New population was evaluated");

            if (solution.getFitness() < population.get(max).getFitness()) {
                solution = population.get(max);

                solvedIn = gen;
            }
            gen++;
            max = 0;
        }
        solvedIn = gen;
    }

    /**
     * Applying the selection
     */
    public static void selection() {
        Selection.apply();
    }

    /**
     * Checks if two individuals are equal
     * 
     * @param parent1
     *            first individual
     * @param parent2
     *            second individual
     * @return true if individuals are equal
     */
    private static boolean areEqual(Individual parent1, Individual parent2) {
        boolean equal = true;
        for (int i = 0; i < parent1.genotype.length; i++) {
            if (parent1.genotype[i] != parent2.genotype[i]) {
                equal = false;
                break;
            }
        }
        return equal;
    }

    /**
     * Breeding process
     */
    public static void breeding() {
        newPopulation = new ArrayList<Individual>();
        for (int i = 0; i < EnvDescription.sizeOfPopulation / 2; i++) {
            Individual parent1 = null;
            Individual parent2 = null;

            parent1 = Selection.getIndividualForBreeding();
            parent2 = Selection.getIndividualForBreeding();
            int loop = 0;
            while (areEqual(parent1, parent2)) {
                parent2 = Selection.getIndividualForBreeding();
                loop++;
                if (loop >= EnvDescription.sizeOfPopulation) {
                    break;
                }
            }

            Crossover.apply(parent1.genotype, parent2.genotype);

            Individual being1 = Individual.newIndividual();
            Individual being2 = Individual.newIndividual();

            being1.define();
            being2.define();
            being1.genotype = Crossover.child1;
            being2.genotype = Crossover.child2;
            newPopulation.add(being1);
            newPopulation.add(being2);

        }
        population = newPopulation;
        mutate();
    }

    /**
     * Mutation process
     */
    public static void mutate() {
        for (int i = 0; i < population.size(); i++) {
            Individual being = population.get(i);
            Individual being1 = Individual.newIndividual();

            being1.define();

            being1.genotype = Mutation.apply(being.genotype);

            population.set(i, being1);
        }
    }

    /**
     * Evaluating population
     */
    public static void evaluatePop() {
        for (int i = 0; i < population.size(); i++) {
            Individual being = Individual.newIndividual();
            being = population.get(i);
            being.evaluate();
            if (being.getFitness() > population.get(max).getFitness()) {
                max = i;
            }
        }
    }

    /**
     * Generating initial population
     */
    public static void generateInitialPopulation() {
        population = new ArrayList<Individual>();
        for (int i = 0; i < EnvDescription.sizeOfPopulation; i++) {
            Individual being = Individual.newIndividual();
            being.generate();
            population.add(being);
        }

    }

    public static void runOnce() throws IOException {
        Enviroment.create();
        ParameterXMLWriter.writeParameters();
        evolute();

        Individual being = Individual.newIndividual();
        being.define();
        being.genotype = solution.genotype;
        solvedFitness = solution.getFitness();
        being.decode();
        being.fenotype.removeUnreachableStates();
        FSMXMLWriter.writeFSM(being.fenotype, EnvDescription.description);
        printRes(being);
        printEvolutionTrace();
    }

    public static void runStat() throws IOException {
        try {
            Enviroment.trace = new PrintWriter(new BufferedWriter(
                    new FileWriter(EnvDescription.traceFile)));
            StringBuffer s = new StringBuffer();

            Enviroment.create();
            ParameterXMLWriter.writeParameters();

            s.append("<html><body>");
            s
                    .append("<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\">");
            s.append("<tr>");
            s.append("<th>Solved in</th>");
            s.append("<th>States</th>");
            s.append("<th>Fitness</th>");
            s.append("</tr>");

            evolute();
            Individual being = Individual.newIndividual();
            being.define();
            being.genotype = solution.genotype;
            solvedFitness = solution.getFitness();
            being.decode();
            being.fenotype.removeUnreachableStates();

            Individual bestBeing = Individual.newIndividual();
            bestBeing.define();
            bestBeing.genotype = being.genotype;
            bestBeing.setFitness(solution.getFitness());
            s.append("<tr>");

            s.append("<td>");
            s.append(solvedIn);
            s.append("</td>");

            s.append("<td>");
            s.append(being.fenotype.getNumOfStates());
            s.append("</td>");
            s.append("<td>");
            s
                    .append(solution.getFitness() / Enviroment.bestFitness
                            * 100 + "%");
            s.append("</td>");
            s.append("</tr>");

            for (int i = 1; i < EnvDescription.runTimes; i++) {
                evolute();
                being = Individual.newIndividual();
                being.define();
                being.genotype = solution.genotype;
                being.decode();
                being.fenotype.removeUnreachableStates();
                being.setFitness(solution.getFitness());
                s.append("<tr>");

                s.append("<td>");
                s.append(solvedIn);
                s.append("</td>");

                s.append("<td>");
                s.append(being.fenotype.getNumOfStates());
                s.append("</td>");
                s.append("<td>");
                s.append(being.getFitness() / Enviroment.bestFitness * 100
                        + "%");
                s.append("</td>");
                s.append("</tr>");
                if (being.getFitness() > bestBeing.getFitness()) {
                    bestBeing = being;
                }

            }
            solvedFitness = bestBeing.getFitness();

            bestBeing.decode();
            bestBeing.fenotype.removeUnreachableStates();
            FSMXMLWriter.writeFSM(bestBeing.fenotype,
                    EnvDescription.description);
            printRes(bestBeing);
            s.append("</table>");
            s.append("</body></html>");
            Enviroment.trace.println(s.toString());

        } catch (IOException e) {
            Enviroment.logger.severe("Error in input/output process...");
        } finally {
            Enviroment.trace.close();
        }

    }

    public static void main(String[] args) throws IOException {
        switch (EnvDescription.action) {
        case EnvDescription.RUN_ONCE:
            runOnce();
            break;

        case EnvDescription.RUN_STAT:
            runStat();
            break;
        }

    }

    public static void run() {
        try {
            main(null);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    /**
     * Checks if the best solution was found
     * 
     * @return true if best solution found
     */
    public static boolean foundTheBest() {
        boolean found = false;
        for (int i = 0; i < population.size(); i++) {
            Individual being = population.get(i);
            if (being.getFitness() == Enviroment.bestFitness) {
                found = true;
                solution = being;
                break;
            }
        }

        return found;
    }

    private static void printRes(Individual being) {
        try {
            Enviroment.results = new PrintWriter(new BufferedWriter(
                    new FileWriter(EnvDescription.resFile)));
            Enviroment.results.println("<html>");
            Enviroment.results.println("<body><font face=\"courier new\">");
            Enviroment.results.println("The solution with fitness "
                    + (solvedFitness / Enviroment.bestFitness * 100)
                    + "% was found in " + solvedIn + " generations<br/>");

            Enviroment.results
                    .println("Parameters of the evolution: "
                            + "<a href=\""+ (new File(EnvDescription.ParamXMLfilename).toURI().toString())+"\" target=\"blank\"> parameters</a><br/>");
            Enviroment.results.println("Log file: "
                    + "<a href=\""+(new File(EnvDescription.LOGfilename).toURI().toString())+"\" target=\"blank\"> log </a><br/>");

            Enviroment.results
                    .println("Solution: "
                            + "<a href=\""+(new File(EnvDescription.FSMXMLfilename).toURI().toString())+"\" target=\"blank\"> fsm</a><br/>");

            Enviroment.results.println("<table border=\"1\">");
            Enviroment.results.println("<caption>Checking data...</caption>");
            Enviroment.results.println("<tr bgcolor=\"#999999\">");
            Enviroment.results.println("<th>Input</th>");
            Enviroment.results.println("<th>Expected output</th>");
            Enviroment.results.println("<th>Produced output</th>");
            Enviroment.results.println("<th>String distance</th>");
            Enviroment.results.println("</tr>");
            for (int i = 0; i < Enviroment.inputData.size(); i++) {
                String ins = "";
                String out1s = "";
                String out2s = "";
                Enviroment.results.println("<tr>");
                ins = Enviroment.inputData.get(i);

                Enviroment.results.println("<td>" + ins + "</td>");
                out1s = Enviroment.outputData.get(i);
                Enviroment.results.println("<td>" + out1s + "</td>");

                being.fenotype.setInString(ins);
                being.fenotype.runMachine();
                out2s = being.fenotype.getOutString();
                being.fenotype.setOutString("");
                Enviroment.results.println("<td>" + out2s + "</td>");
                int hd = StringDistances.apply(out1s, out2s);
                Enviroment.results.println("<td>" + (out1s.length() - hd)
                        + "</td>");
                Enviroment.results.println("</tr>");

            }
            Enviroment.results.println("</table>");
            Enviroment.results.println("</font>");

            Enviroment.results.println("</body>");
            Enviroment.results.println("</html>");

        } catch (IOException e) {
            Enviroment.logger.severe("Error in input/output process...");
        } finally {
            Enviroment.results.close();
        }
    }
}
